﻿/*  ----------------------------------------------------------------------------
 *  SysServer
 *  ----------------------------------------------------------------------------
 *  File:       SocketListener.cs
 *  Creater:     SEEN\MistSeen
 *  ----------------------------------------------------------------------------
 *  $Date: 2010-09-06 10:36:01 +0000 (Mon, 06 Sep 2010) $
 *  $Rev: 15 $
 *  $Author: ScorDeity $
 *  $URL: http://sysserver.googlecode.com/svn/trunk/src/SysServer/Sock/SocketListener.cs $
 *  $Id: SocketListener.cs 15 2010-09-06 10:36:01Z ScorDeity $
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace SysServer.Sock
{
    using System.Net.Sockets;
    using System.Threading;
    /// <summary>
    /// 
    /// </summary>
    public class SocketListener
    {
        private int _TotalBytesRead;
        private int _numConnectedSockets;

        private static object LockerForDic = new object();
        private object LockForConnectCount = new object();

        private BufferManager _BufferManager;
        private Socket _ListenSocket;

        /// <summary>
        ///This Semaphore is used to keep from going over max connection #. (It is not about 
        ///controlling threading really here.) 
        /// </summary>
        private Semaphore _MaxContectCountSemaphore;

        private SocketListenerSetting _SocketListenerSetting;

        private SocketAsyncEventArgsPool _AcceptEventArgs;
        private SocketAsyncEventArgsPool _RecSendEventArgs;


        public SocketListener(SocketListenerSetting setting)
        {
            this._SocketListenerSetting = setting;

            this._BufferManager = new BufferManager(
                setting.BufferSize * setting.MaxConnectionCount * setting.OpsToPreAllocate,
                setting.BufferSize * setting.OpsToPreAllocate
                );

            this._AcceptEventArgs = new SocketAsyncEventArgsPool(setting.MaxAcceptConnectionCount);
            this._RecSendEventArgs = new SocketAsyncEventArgsPool(setting.MaxConnectionCount);
            this._MaxContectCountSemaphore = new Semaphore(setting.MaxConnectionCount + 1, setting.MaxConnectionCount + 1);

            this.InitLister();
            this.StartLister();
        }


        public void InitLister()
        {
            this._BufferManager.InitBuffer();
            for (int i = 0; i < this._SocketListenerSetting.MaxAcceptConnectionCount; i++)
            {
                this._AcceptEventArgs.Push(CrateNewArgForAccept(this._AcceptEventArgs));
            }
            SocketAsyncEventArgs recSendArgForPool;
            for (int i = 0; i < this._SocketListenerSetting.MaxConnectionCount; i++)
            {
                recSendArgForPool = new SocketAsyncEventArgs();
                this._BufferManager.SetBuffer(recSendArgForPool);
                recSendArgForPool.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                DataEntityUserToken tempReceiveSendUserToken = new DataEntityUserToken(
                    recSendArgForPool,
                    recSendArgForPool.Offset, recSendArgForPool.Offset + this._SocketListenerSetting.BufferSize,
                    this._SocketListenerSetting.ReceivePrefixLength,
                    this._SocketListenerSetting.SendPrefixLength,
                    i + 1);

                tempReceiveSendUserToken.CreateNewDateEntiy();
                recSendArgForPool.UserToken = tempReceiveSendUserToken;
                this._RecSendEventArgs.Push(recSendArgForPool);
            }
        }
        public void StartLister()
        {
            // create the socket which listens for incoming connections
            this._ListenSocket = new Socket(this._SocketListenerSetting.LocalEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            //bind it to the port
            this._ListenSocket.Bind(this._SocketListenerSetting.LocalEndPoint);
            // Start the listener with a backlog of however many connections.
            //"backlog" means pending connections. 
            //The backlog number is the number of clients that can wait for a
            //SocketAsyncEventArg object that will do an accept operation.
            //The listening socket keeps the backlog as a queue. The backlog allows 
            //for a certain # of excess clients waiting to be connected.
            //If the backlog is maxed out, then the client will receive an error when
            //trying to connect.
            //max # for backlog can be limited by the operating system.
            this._ListenSocket.Listen(this._SocketListenerSetting.Backlog);

            this.StartAccept();
        }

        private void StartAccept()
        {
            //Get a SocketAsyncEventArgs object to accept the connection.
            SocketAsyncEventArgs acceptEventArg;
            //Get it from the pool if there is more than one in the pool.
            if (this._AcceptEventArgs.Count > 1)
            {
                try
                {
                    acceptEventArg = this._AcceptEventArgs.Pop();
                }
                catch
                {
                    acceptEventArg = new SocketAsyncEventArgs();
                }
            }
            else
            {
                acceptEventArg = CrateNewArgForAccept(this._AcceptEventArgs);
            }
            //Semaphore class is used to control access to a resource or pool of 
            //resources. Enter the semaphore by calling the WaitOne method, which is 
            //inherited from the WaitHandle class, and release the semaphore 
            //by calling the Release method. This is a mechanism to prevent exceeding
            // the max # of connections we specified. We'll do this before
            // doing AcceptAsync. If maxConnections value has been reached,
            //then the application will pause here until the Semaphore gets released,
            //which happens in the CloseClientSocket method.
            this._MaxContectCountSemaphore.WaitOne();

            //Socket.AcceptAsync begins asynchronous operation to accept the connection.
            //Note the listening socket will pass info to the SocketAsyncEventArgs
            //object that has the Socket that does the accept operation.
            //If you do not create a Socket object and put it in the SAEA object
            //before calling AcceptAsync and use the AcceptSocket property to get it,
            //then a new Socket object will be created for you by .NET.    
            bool willRaiseEvent = this._ListenSocket.AcceptAsync(acceptEventArg);

            //Socket.AcceptAsync returns true if the I/O operation is pending, i.e. is 
            //working asynchronously. The 
            //SocketAsyncEventArgs.Completed event on the acceptEventArg parameter 
            //will be raised upon completion of accept op.
            //AcceptAsync will call the AcceptEventArg_Completed
            //method when it completes, because when we created this SocketAsyncEventArgs
            //object before putting it in the pool, we set the event handler to do it.
            //AcceptAsync returns false if the I/O operation completed synchronously.            
            //The SocketAsyncEventArgs.Completed event on the acceptEventArg parameter will NOT be raised.

            if (!willRaiseEvent)
            {
                //The code in this if (!willRaiseEvent) statement only runs 
                //when the operation was completed synchronously. It is needed because 
                //when Socket.AcceptAsync returns false, 
                //it does NOT raise the SocketAsyncEventArgs.Completed event.
                //And we need to call ProcessAccept and pass it the SAEA object.
                //This is only when a new connection is being accepted.
                ProcessAccept(acceptEventArg);
            }
        }

        private void ProcessAccept(SocketAsyncEventArgs acceptEventArg)
        {
            lock (this.LockForConnectCount)
            {
                this._numConnectedSockets++;
            }
            //Now that the accept operation completed, we can start another
            //accept operation, which will do the same. Notice that we are NOT
            //passing the SAEA object here.
            LoopToStartAccept();


            // Get a SocketAsyncEventArgs object from the pool of receive/send op 
            //SocketAsyncEventArgs objects
            SocketAsyncEventArgs receiveSendEventArg = this._RecSendEventArgs.Pop();

            ((DataEntityUserToken)receiveSendEventArg.UserToken).CreateSessionId();
            //A new socket was created by the AcceptAsync method. The 
            //SocketAsyncEventArgs object which did the accept operation has that 
            //socket info in its AcceptSocket property. Now we will give
            //a reference for that socket to the SocketAsyncEventArgs 
            //object which will do receive/send.
            receiveSendEventArg.AcceptSocket = acceptEventArg.AcceptSocket;
            //We have handed off the connection info from the
            //accepting socket to the receiving socket. So, now we can
            //put the SocketAsyncEventArgs object that did the accept operation 
            //back in the pool for them. But first we will clear 
            //the socket info from that object, so it will be 
            //ready for a new socket when it comes out of the pool.
            acceptEventArg.AcceptSocket = null;
            this._AcceptEventArgs.Push(acceptEventArg);
            StartReceive(receiveSendEventArg);
        }

        private void StartReceive(SocketAsyncEventArgs receiveSendEventArg)
        {
            bool willRaiseEvent = receiveSendEventArg.AcceptSocket.ReceiveAsync(receiveSendEventArg);
            //Socket.ReceiveAsync returns true if the I/O operation is pending. The 
            //SocketAsyncEventArgs.Completed event on the e parameter will be raised 
            //upon completion of the operation. So, true will cause the IO_Completed
            //method to be called when the receive operation completes. 
            //That's because of the event handler we created when building
            //the pool of SocketAsyncEventArgs objects that perform receive/send.
            //It was the line that said
            //eventArgObjectForPool.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);

            //Socket.ReceiveAsync returns false if I/O operation completed synchronously. 
            //In that case, the SocketAsyncEventArgs.Completed event on the e parameter 
            //will not be raised and the e object passed as a parameter may be 
            //examined immediately after the method call 
            //returns to retrieve the result of the operation.
            // It may be false in the case of a socket error.
            if (!willRaiseEvent)
            {
                //If the op completed synchronously, we need to call ProcessReceive 
                //method directly. This will probably be used rarely, as you will 
                //see in testing.
                ProcessReceive(receiveSendEventArg);
            }
        }

        private void ProcessReceive(SocketAsyncEventArgs receiveSendEventArg)
        {
            var receiverSendToken = (DataEntityUserToken)receiveSendEventArg.UserToken;

            bool inComingTcpMessageIsReady = false;

            if (receiveSendEventArg.BytesTransferred > 0 && receiveSendEventArg.SocketError == SocketError.Success)
            {
                Interlocked.Add(ref this._TotalBytesRead, receiveSendEventArg.BytesTransferred);

                int recPrefixBytesDoneThisOp = 0;
                int remainingBytesToProcess = receiveSendEventArg.BytesTransferred;
                //If we have not got all of the prefix already, 
                //then we need to work on it here.     
                if (receiverSendToken.ReceivedPrefixBytesDoneCount < this._SocketListenerSetting.ReceivePrefixLength)
                {
                    //receivedPrefixBytesDoneCount tells us how many prefix bytes were
                    //processed during previous receive ops which contained data for 
                    //this message. Usually there will NOT have been any previous 
                    //receive ops here. So in that case,
                    //receiveSendToken.receivedPrefixBytesDoneCount would equal 0.
                    //Create a byte array to put the new prefix in, if we have not
                    //already done it in a previous loop.

                    if (receiverSendToken.ReceivedPrefixBytesDoneCount == 0)
                    {
                        receiverSendToken.PrefixBuffer = new byte[this._SocketListenerSetting.ReceivePrefixLength];
                    }
                    //If this next if-statement is true, then we have received at 
                    //least enough bytes to have the prefix. So we can determine the 
                    //length of the message that we are working on.
                    if (remainingBytesToProcess >= this._SocketListenerSetting.ReceivePrefixLength - receiverSendToken.ReceivedPrefixBytesDoneCount)
                    {
                        //Now copy that many bytes to byteArrayForPrefix.
                        //We can use the variable receiveMessageOffset as our main
                        //index to show which index to get data from in the TCP
                        //buffer.
                        Buffer.BlockCopy(receiveSendEventArg.Buffer,
                            receiverSendToken.ReceivedMessageOffSet - this._SocketListenerSetting.ReceivePrefixLength + receiverSendToken.ReceivedPrefixBytesDoneCount,
                            receiverSendToken.PrefixBuffer,
                            receiverSendToken.ReceivedPrefixBytesDoneCount,
                            this._SocketListenerSetting.ReceivePrefixLength - receiverSendToken.ReceivedPrefixBytesDoneCount);

                        remainingBytesToProcess = remainingBytesToProcess - this._SocketListenerSetting.ReceivePrefixLength + receiverSendToken.ReceivedPrefixBytesDoneCount;

                        recPrefixBytesDoneThisOp = this._SocketListenerSetting.ReceivePrefixLength - receiverSendToken.ReceivedPrefixBytesDoneCount;
                        receiverSendToken.ReceivedPrefixBytesDoneCount = this._SocketListenerSetting.ReceivePrefixLength;
                        receiverSendToken.MessageEntityLenght = BitConverter.ToInt32(receiverSendToken.PrefixBuffer, 0);
                    }
                    //This next else-statement deals with the situation 
                    //where we have some bytes
                    //of this prefix in this receive operation, but not all.
                    else
                    {   //Write the bytes to the array where we are putting the
                        //prefix data, to save for the next loop.
                        Buffer.BlockCopy(receiveSendEventArg.Buffer,
                                         receiverSendToken.ReceivedMessageOffSet - this._SocketListenerSetting.ReceivePrefixLength - receiverSendToken.ReceivedPrefixBytesDoneCount,
                                         receiverSendToken.PrefixBuffer,
                                         receiverSendToken.ReceivedPrefixBytesDoneCount,
                                         remainingBytesToProcess);
                        recPrefixBytesDoneThisOp = remainingBytesToProcess - receiverSendToken.ReceivedPrefixBytesDoneCount;
                        remainingBytesToProcess = 0;
                        receiverSendToken.ReceivedMessageOffSet = receiverSendToken.ReceivedMessageOffSet - receiverSendToken.ReceivedPrefixBytesDoneCount;
                        StartReceive(receiveSendEventArg);
                    }
                }

                //If we have processed the prefix, we can work on the message now,
                //if there are more bytes to work on.
                if ((receiverSendToken.ReceivedPrefixBytesDoneCount == this._SocketListenerSetting.ReceivePrefixLength) & (remainingBytesToProcess > 0))
                {
                    //Create the array where we'll store the complete message, 
                    //if it has not been created on a previous receive op or loop
                    //through the while loop.
                    if (receiverSendToken.DataEntity.ReceivedData == null)
                    {
                        //We'll be inside this if-statement the first time that we 
                        //receive enough data to have the 
                        //prefix, which carries the length information for the message
                        //that we are currently working on.                        
                        receiverSendToken.DataEntity.ReceivedData = new Byte[receiverSendToken.MessageEntityLenght];
                    }
                    //Remember there is a receiveSendToken.receivedPrefixBytesDoneCount
                    //variable, which allows us to handle the prefix even when it
                    //requires multiple receive ops. in the same way, we have a 
                    //receiveSendToken.receivedMessageBytesDoneCount variable, which
                    //helps us handle message data, whether it requires one receive
                    //operation or many.

                    if (remainingBytesToProcess + receiverSendToken.ReceivedMessageBytesDoneCount == receiverSendToken.MessageEntityLenght)
                    {
                        //Write/append the bytes received to the byte array in the 
                        //DataHolder object that we are using to store our data.
                        Buffer.BlockCopy(receiveSendEventArg.Buffer, receiverSendToken.ReceivedMessageOffSet,
                                         receiverSendToken.DataEntity.ReceivedData,
                                         receiverSendToken.ReceivedMessageBytesDoneCount,
                                         remainingBytesToProcess);
                        inComingTcpMessageIsReady = true;

                        //Reset the variables in the UserToken, to be ready for the
                        //next message that will be received on the socket in this
                        //SAEA object.

                        receiverSendToken.ReceivedPrefixBytesDoneCount = 0;
                        receiverSendToken.ReceivedMessageBytesDoneCount = 0;
                        remainingBytesToProcess = 0;
                        receiverSendToken.ReceivedMessageOffSet = receiverSendToken.BufferOffsetReceive + this._SocketListenerSetting.ReceivePrefixLength;
                    }
                    else if (remainingBytesToProcess + receiverSendToken.ReceivedMessageBytesDoneCount < receiverSendToken.MessageEntityLenght)
                    {
                        Buffer.BlockCopy(receiveSendEventArg.Buffer,
                                         receiverSendToken.ReceivedMessageOffSet,
                                         receiverSendToken.DataEntity.ReceivedData,
                                         receiverSendToken.ReceivedMessageBytesDoneCount,
                                         remainingBytesToProcess);
                        receiverSendToken.ReceivedMessageOffSet = receiverSendToken.ReceivedMessageOffSet - recPrefixBytesDoneThisOp;
                        receiverSendToken.ReceivedMessageBytesDoneCount = receiverSendToken.ReceivedMessageBytesDoneCount + remainingBytesToProcess;
                        remainingBytesToProcess = 0;

                        StartReceive(receiveSendEventArg);
                    }
                }
                if (inComingTcpMessageIsReady == true)
                {
                    //Pass the DataHolder object to the Mediator here.
                    // The data in this DataHolder can be used for all kinds of 
                    //things that an intelligent and
                    //creative person like you might think of.                        
                    receiverSendToken.Mediator.HandleData(receiverSendToken.DataEntity);
                    receiverSendToken.CreateNewDateEntiy();
                    inComingTcpMessageIsReady = false;
                    receiverSendToken.Mediator.PrepareOutData();
                    StartSend(receiverSendToken.Mediator.CallBackSocketAsyncEventArg);
                }
            }
            else
            //If no data was received, close the connection.
            {
                CloseClientSocket(receiveSendEventArg);
            }

        }




        private void LoopToStartAccept()
        {
            StartAccept();
        }
        private void StartSend(SocketAsyncEventArgs receiveSendEventArg)
        {
            var receiveSendToken = (DataEntityUserToken)receiveSendEventArg.UserToken;
            //Set the buffer. You can see on Microsoft's page at 
            //http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.setbuffer.aspx
            //that there are two overloads. One of the overloads has 3 parameters.
            //When setting the buffer, you need 3 parameters the first time you set it,
            //which we did in the Init method. The first of the three parameters
            //tells what byte array to use as the buffer. After we tell what byte array
            //to use we do not need to use the overload with 3 parameters any more.
            //(That is the whole reason for using the buffer block. You keep the same
            //byte array as buffer always, and keep it all in one block.)
            //Now we use the overload with two parameters. We tell 
            // (1) the offset and
            // (2) the number of bytes to use, starting at the offset.

            //The number of bytes to send depends on whether the message is larger than
            //the buffer or not. If it is larger than the buffer, then we will have
            //to post more than one send operation. If it is less than or equal to the
            //size of the send buffer, then we can accomplish it in one send op.
            if (receiveSendToken._SendBytesRemainingCount <= this._SocketListenerSetting.BufferSize)
            {
                receiveSendEventArg.SetBuffer(receiveSendToken.BufferOffsetSend, receiveSendToken._SendBytesRemainingCount);
                //Copy the bytes to the buffer associated with this SAEA object.
                Buffer.BlockCopy(receiveSendToken._DataToSend, receiveSendToken._BytesSentAlreadyCount, receiveSendEventArg.Buffer, receiveSendToken.BufferOffsetSend, receiveSendToken._SendBytesRemainingCount);
            }
            else
            {
                //We cannot try to set the buffer any larger than its size.
                //So since receiveSendToken.sendBytesRemainingCount > BufferSize, we just
                //set it to the maximum size, to send the most data possible.
                receiveSendEventArg.SetBuffer(receiveSendToken.BufferOffsetSend, this._SocketListenerSetting.BufferSize);
                Buffer.BlockCopy(receiveSendToken._DataToSend, receiveSendToken._BytesSentAlreadyCount, receiveSendEventArg.Buffer, receiveSendToken.BufferOffsetSend, this._SocketListenerSetting.BufferSize);
            }
            //post asynchronous send operation
            bool willRaiseEvent = receiveSendEventArg.AcceptSocket.SendAsync(receiveSendEventArg);
            if (!willRaiseEvent)
            {
                ProcessSend(receiveSendEventArg);
            }
        }

        private void ProcessSend(SocketAsyncEventArgs receiveSendEventArg)
        {
            var receiveSendToken = (DataEntityUserToken)receiveSendEventArg.UserToken;

            receiveSendToken._SendBytesRemainingCount = receiveSendToken._BytesSentAlreadyCount - receiveSendEventArg.BytesTransferred;
            receiveSendToken._BytesSentAlreadyCount += receiveSendEventArg.BytesTransferred;

            if (receiveSendEventArg.SocketError == SocketError.Success)
            {
                if (receiveSendToken._SendBytesRemainingCount == 0)
                {
                    //If we are within this if-statement, then all the bytes in the message
                    //have been sent. So now we need to reset the buffer for the receive
                    //operation.
                    receiveSendEventArg.SetBuffer(receiveSendToken.BufferOffsetReceive, this._SocketListenerSetting.BufferSize);
                    StartReceive(receiveSendEventArg);
                }
                else
                {
                    //If some of the bytes in the message have NOT been sent,
                    //then we will need to post another send operation.
                    //So let's loop back to StartSend().
                    StartSend(receiveSendEventArg);

                }
            }
            else
            {
                CloseClientSocket(receiveSendEventArg);
            }
        }
        private void CloseClientSocket(SocketAsyncEventArgs receiveSendEventArg)
        {
            var receiveSendToken = (DataEntityUserToken)receiveSendEventArg.UserToken;
            // do a shutdown before you close the socket
            try
            {
                receiveSendEventArg.AcceptSocket.Shutdown(SocketShutdown.Both);
            }
            catch (Exception ex)
            {
            }
            finally
            {
                receiveSendEventArg.AcceptSocket.Close();
                if (receiveSendToken.DataEntity.ReceivedData != null)
                {
                    receiveSendToken.CreateNewDateEntiy();
                }
                this._RecSendEventArgs.Push(receiveSendEventArg);
                //Release Semaphore so that its connection counter will be decremented.
                //This must be done AFTER putting the SocketAsyncEventArg back into the pool,
                //or you can run into problems.
                this._MaxContectCountSemaphore.Release();

            }

        }
        private SocketAsyncEventArgs CrateNewArgForAccept(SocketAsyncEventArgsPool socketAsyncEventArgsPool)
        {
            SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();
            acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
            AcceptUserToken acceptUserToken = new AcceptUserToken(socketAsyncEventArgsPool.GetTokenId());
            acceptEventArg.UserToken = acceptUserToken;
            return acceptEventArg;
        }

        private void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            //Any code that you put in this method will NOT be called if
            //the operation completes synchronously, which will probably happen when
            //there is some kind of socket error. It might be better to put the code
            //in the ProcessAccept method.

            ProcessAccept(e);
        }

        private void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            //Any code that you put in this method will NOT be called if
            //the operation completes synchronously, which will probably happen when
            //there is some kind of socket error.

            var receiveSendToken = (DataEntityUserToken)e.UserToken;
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
                default:
                    //This exception will occur if you code the Completed event of some
                    //operation to come to this method, by mistake.
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");

            }
        }

    }
}